package com.atghost.miniofile.util;

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Admin
 * @version 1.0
 * @date 2024-5-19 14:33
 **/
@Component
@Getter
public class MinioFileUtil {
    private static Log logger = LogFactory.getLog(MinioFileUtil.class);

    @Value("${minio.endpoint:http://192.168.10.131:9002}")
    private String minioEndpoint;
    @Value("${minio.accessKey:minioadmin}")
    private String minioAccessKey;
    @Value("${minio.secretKey:minioadmin}")
    private String minioSecretKey;
    @Value("${minio.file-show-url:1}")
    private String showUrl;

    /**
     * @description 获取minioClient
     */
    public MinioClient getMinioClient() {

        logger.info("……………minioEndpoint……………" + minioEndpoint);
        logger.info("……………minioAccessKey……………" + minioAccessKey);
        logger.info("……………minioSecretKey……………" + minioSecretKey);
        return MinioClient.builder()
                .endpoint(minioEndpoint)
                .credentials(minioAccessKey, minioSecretKey)
                .build();
    }

    /**
     * @description 将分钟数转换为秒数
     * @Param expiry 过期时间（分钟）
     */
    private int expiryHandle(Integer expiry) {
        expiry = expiry * 60;
        if (expiry > 604800) {
            return 604800;
        }
        return expiry;
    }

    /**
     * @param inputStream 文件流
     * @param bucketName  桶名称
     * @param directory   文件存储目录
     * @param objectName  文件名称
     * @description 文件上传至指定桶容器，并返回对象文件的存储路径加文件名
     */
    @SneakyThrows
    public String uploadObject(InputStream inputStream, String bucketName, String directory, String objectName) {
        if (StringUtils.isNotEmpty(directory)) {
            objectName = directory + "/" + objectName;
        }
        getMinioClient().putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .stream(inputStream, inputStream.available(), -1)
                .build());
        return objectName;
    }

    /**
     * @param bucketName 桶名称
     * @param objectName 文件名称(包含存储目录)
     * @param expiry     过期时间(分钟) 最大为7天 超过7天则默认最大值
     * @description 获取访问对象的url地址
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName, Integer expiry) {
        expiry = expiryHandle(expiry);
        String url = getMinioClient().getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(bucketName)
                        .object(objectName)
                        .expiry(expiry)
                        .build());
        if (!showUrl.equals("1") && showUrl.length() > 2) {
            url = url.replace(minioEndpoint, showUrl);
        }
        return url;
    }

    /**
     * @param bucketName 桶名称
     * @param objectName 文件路径
     * @description 获取某个文件
     */
    @SneakyThrows
    public StatObjectResponse getObjectInfo(String bucketName, String objectName) {
        return getMinioClient().statObject(StatObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
    }

    /**
     * @param bucketName 桶名称
     * @param objectName 文件名称(包含存储目录)
     * @description 删除一个对象文件
     */
    public boolean removeObject(String bucketName, String objectName) {
        try {
            getMinioClient().removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName).build());
            return true;
        } catch (Exception e) {
            logger.error("removeObject error", e);
            return false;
        }
    }

    /**
     * @param bucketName  桶名称
     * @param objectNames 文件名称(包含存储目录)
     * @description 删除多个对象文件
     */
    @SneakyThrows
    public List<String> removeObjects(String bucketName, List<String> objectNames) {
        if (!bucketExists(bucketName)) {
            return new ArrayList<>();
        }
        List<String> deleteErrorNames = new ArrayList<>();
        List<DeleteObject> deleteObjects = new ArrayList<>(objectNames.size());
        for (String objectName : objectNames) {
            deleteObjects.add(new DeleteObject(objectName));
        }
        Iterable<Result<DeleteError>> results = getMinioClient().removeObjects(
                RemoveObjectsArgs.builder()
                        .bucket(bucketName)
                        .objects(deleteObjects)
                        .build());
        for (Result<DeleteError> result : results) {
            DeleteError error = result.get();
            deleteErrorNames.add(error.objectName());
        }
        return deleteErrorNames;
    }

    /**
     * @param bucketName 桶名称
     * @description 判断bucket是否存在
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        boolean exists = false;
        BucketExistsArgs.Builder builder = BucketExistsArgs.builder();
        BucketExistsArgs build = builder.bucket(bucketName).build();
        exists = getMinioClient().bucketExists(build);
        return exists;
    }

    /**
     * @param bucketName 桶名称
     * @description 创建存储桶
     * minio 桶设置公共或私有，alioss统一设置成私有，可配置文件公共读或私有读
     */
    @SneakyThrows
    public void makeBucket(String bucketName) {
        if (bucketExists(bucketName)) {
            return;
        }
        getMinioClient().makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * @param bucketName 桶名称
     * @param objectName 文件路径
     * @param offset     截取流的开始位置
     * @param length     截取长度
     * @description 获取文件
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName, Long offset, Long length) {
        logger.info("……………bucketName……………" + bucketName);
        logger.info("……………objectName……………" + objectName);
        logger.info("……………offset……………" + offset);
        logger.info("……………length……………" + length);

        return getMinioClient().getObject(
                GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());
    }

    /**
     * @param bucketName 桶名称
     * @param objectName 文件路径
     * @description 获取文件
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName) {
        return getMinioClient().getObject(
                GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * @param inputStream 输入流
     * @param objectName  文件路径
     * @param bucketName  桶名称
     * @description 上传分片文件
     */
    @SneakyThrows
    public void putChunkObject(InputStream inputStream, String bucketName, String objectName) {
        try {
            getMinioClient().putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(inputStream, inputStream.available(), -1)
                            .build());
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * @param bucketName 桶名称
     * @description 删除空桶
     */
    @SneakyThrows
    public void removeBucket(String bucketName) {
        removeObjects(bucketName, listObjectNames(bucketName));
        getMinioClient().removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * @param bucketName 桶名称
     * @description 查询桶中所有的文件
     */
    @SneakyThrows
    public List<String> listObjectNames(String bucketName) {
        List<String> objectNameList = new ArrayList<>();
        if (bucketExists(bucketName)) {
            Iterable<Result<Item>> objects = getMinioClient().listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).recursive(true).build());
            for (Result<Item> result : objects) {
                Item item = result.get();
                objectNameList.add(item.objectName());
            }
        }
        return objectNameList;
    }

    /**
     * @param originBucketName 分块文件所在的桶
     * @param targetBucketName 合并文件生成文件所在的桶
     * @param objectName       存储于桶中的对象名
     * @description 文件合并
     */
    @SneakyThrows
    public String composeObject(String originBucketName, String targetBucketName, String objectName) {
        Iterable<Result<Item>> results = getMinioClient().listObjects(
                ListObjectsArgs.builder().bucket(originBucketName).recursive(true).build());
        List<String> objectNameList = new ArrayList<>();
        for (Result<Item> result : results) {
            Item item = result.get();
            objectNameList.add(item.objectName());
        }
        if (ObjectUtils.isEmpty(objectNameList)) {
            throw new IllegalArgumentException(originBucketName + "桶中没有文件，请检查");
        }
        List<ComposeSource> composeSourceList = new ArrayList<>(objectNameList.size());
        // 对文件名集合进行升序排序
        objectNameList.sort((o1, o2) -> Integer.parseInt(o2) > Integer.parseInt(o1) ? -1 : 1);
        for (String object : objectNameList) {
            composeSourceList.add(ComposeSource.builder()
                    .bucket(originBucketName)
                    .object(object)
                    .build());
        }

        return composeObject(composeSourceList, targetBucketName, objectName);
    }

    /**
     * @param bucketName       合并文件生成文件所在的桶
     * @param objectName       原始文件名
     * @param sourceObjectList 分块文件集合
     * @description 文件合并
     */
    @SneakyThrows
    public String composeObject(List<ComposeSource> sourceObjectList, String bucketName, String objectName) {
        getMinioClient().composeObject(ComposeObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .sources(sourceObjectList)
                .build());
        return getObjectUrl(bucketName, objectName, 100);
    }
}
